/**************************************************************************(C)*
 *
 *  This file is part of the GreenHills project
 *  Copyright (C) 2009  Tieto Corporation
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  Not for commmercial use. Only with Tieto permission.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *************************************************************************(C)*/

#include "../nmeaparser.h"

#include <QStringList>
#include <QTime>

using namespace greenhills::gps;

CNMEAParser::CNMEAParser(QObject *pParent)
    :QObject(pParent)
{
}

void CNMEAParser::onLineReceived(const QString &pLine)
{
    int checksumPos = pLine.lastIndexOf('*');
    if (checksumPos<0)
    {
        qDebug("Invalid NMEA sentence received");
        return;
    }

    bool ok;
    int receivedChecksum = pLine.mid(checksumPos+1).toInt(&ok, 16);

    int calculatedChecksum = checksum(pLine);

    if (!ok || receivedChecksum != calculatedChecksum)
    {
        qDebug("Invalid NMEA sentence received - wrong checksum");
        return;
    }


    QString nmeaSentence = pLine.left(checksumPos);

    if (nmeaSentence.startsWith("$GPGGA"))
        onGPGGASentenceReceived(nmeaSentence);
    else if (nmeaSentence.startsWith("$GPVTG"))
        onGPVTGSentenceReceived(nmeaSentence);
}

void CNMEAParser::onGPGGASentenceReceived(const QString &pLine)
{
    enum GPGGASentenceParameters {
        ggaIdentifier = 0,
        ggaTime,
        ggaLatitude,
        ggaNorthOrSouth,
        ggaLongitude,
        ggaEastOrWest,
        ggaFixQuality,
        ggaSatelitesCount,
        ggaHdop,
        ggaAltitude,
        ggaReserved1,
        ggaGeoidHeight,
        ggaReserved2,
        ggaLastDGPSUpdateDelay,
        ggaDGPSReferenceStationId,
        ggaItemsCount,
    };

    QStringList list = pLine.split(",");
    if (list.count() != ggaItemsCount)
    {
        qDebug("Invalid NMEA GPGGA sentence received");
        return;
    }

    //Time
    QTime time = QTime::fromString(list[ggaTime], "hhmmss.zzz");
    if (!time.isValid())
        time = QTime::fromString(list[ggaTime], "hhmmss");

    //GPS fix
    CGPSFix fix((CGPSFix::eGPSfix)list[ggaFixQuality].toInt());

    //GPS coordinates
    CGPSPosition position;
    switch(fix)
    {
    case 0: //fix not valid
        break;
    case 1:
    case 2:
        if(!list[ggaNorthOrSouth].isEmpty() && !list[ggaEastOrWest].isEmpty())
        {
            position = CGPSPosition (
                GPSCoordinateFromNMEA(
                        list[ggaLatitude].toFloat(),
                        list[ggaNorthOrSouth].at(0).toAscii(),
                        'N','S'),
                GPSCoordinateFromNMEA(
                        list[ggaLongitude].toFloat(),
                        list[ggaEastOrWest].at(0).toAscii(),
                        'E','W'));
        }
        break;
    }

    //Satelites count
    int satelites = list[ggaSatelitesCount].toInt();

    //Satelites count
    float hdop = list[ggaHdop].toFloat();

    //Altitude
    float altitude = list[ggaAltitude].toFloat();

    //Geoid height
    float geoidHeight = list[ggaGeoidHeight].toFloat();

    //Last DGPS Update Delay
    float dgpsUpdateDelay = list[ggaLastDGPSUpdateDelay].toFloat();

    //DPGS Reference Station Id
    int dgpsStationId = list[ggaDGPSReferenceStationId].toInt();

#ifdef EMULATE_GPS
        position = gps::CGPSPosition(
                gps::CGPSCoordinate(RAND(4811, 4813)),
                gps::CGPSCoordinate(RAND(1852, 1859)));
        fix = CGPSFix::DGPSfix;
        satelites = (int)RAND( 5, 10);
        hdop = RAND( 1, 3);
        altitude = RAND( 230, 280);
        geoidHeight = RAND( 230, 280);
        dgpsUpdateDelay = RAND(0, 1000);
        dgpsStationId = (int)RAND(0, 10);
#endif

    Q_EMIT( GPGGAreceived(time, position, fix, satelites, hdop, altitude,
        geoidHeight, dgpsUpdateDelay, dgpsStationId) );
}

void CNMEAParser::onGPVTGSentenceReceived(const QString &pLine)
{
    enum GPVTGSentenceParameters {
        vtgIdentifier = 0,
        vtgHeadingTrueNorth,
        vtgHeadingTrueNorthIdentifier,
        vtgHeadingMagnetic,
        vtgHeadingMagneticIdentifier,
        vtgSpeedInKnots,
        vtgSpeedInKnotsIdentifier,
        vtgSpeedInKilometers,
        vtgSpeedInKilometersIdentifier,
        vtgReserved,
        vtgItemsCount,
    };

    QStringList list = pLine.split(",");
    if (list.count() != vtgItemsCount)
    {
        qDebug("Invalid NMEA GPGGA sentence received");
        return;
    }

    //Satelites count
    float headingTrueNorth = (list[vtgHeadingTrueNorthIdentifier] == "T") ?
        list[vtgHeadingTrueNorth].toFloat() : 0;

    float headingMagnetic = (list[vtgHeadingMagneticIdentifier] == "M") ?
        list[vtgHeadingMagnetic].toFloat() : 0;

    float speedInKnots = (list[vtgSpeedInKnotsIdentifier] == "N") ?
        list[vtgSpeedInKnots].toFloat() : 0;

    float speedInKilometers = (list[vtgSpeedInKilometersIdentifier] == "K") ?
        list[vtgSpeedInKilometers].toFloat() : 0;

#ifdef EMULATE_GPS
        headingTrueNorth = RAND(0, 60);
        headingMagnetic = RAND(10, 70);
        speedInKnots = RAND(0, 60);
        speedInKilometers = RAND(0, 20);
#endif

    Q_EMIT( GPVTGreceived( headingTrueNorth, headingMagnetic,
        speedInKnots, speedInKilometers) );
}

CGPSCoordinate CNMEAParser::GPSCoordinateFromNMEA(
        float pValue, char pDirection, char pPosDirection, char pNegDirection)
{
    Q_ASSERT(pDirection==pNegDirection || pDirection==pPosDirection);

    //! \todo Replace with constants from gpscoordinate.cpp
    long s = (long)pValue/100;
    long m = (long)((pValue-s*100)*10000);
    long x = s*60*10000+m;

    return CGPSCoordinate(x * ((pDirection==pPosDirection) ? 1 : -1));
}

unsigned char CNMEAParser::checksum(const QString &pNmeaSentence)
  {
    unsigned char checksum = 0;


    for(QString::const_iterator it = pNmeaSentence.begin();
        it != pNmeaSentence.end(); it++)
    {
        if (*it == '$')
            continue;
        if (*it == '*')
            break;

        char character = it->toAscii();
        if (character < 0)
            return 0;

        checksum = (checksum == 0) ? character : (checksum ^ character);
    }

    return checksum;
  }

